﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;

using RayDen.Library.Core.Primitives;
using RayDen.Library.Entity;
using RayDen.Library.Entity.Scene;

namespace RayDen.Library.Data.Import.OBJLoader
{
    [EntityAction(ActionName = "LoadMtl")]
    public class MaterialLoader : IMaterialLoader
    {
        public delegate void ParseHandler(string line, string[] tokens);

        #region IMaterialLoader Members

        public MaterialInfo[] LoadMaterials(string fileName, params string[] paths)
        {
            var parser = new MTLParser(fileName);
            return parser.mMtrls.Select(objMaterial => CreateMaterial(objMaterial.Key, objMaterial.Value)).ToArray();
        }
        public MaterialInfo[] Get()
        {
            return null;
        }

        #endregion


        protected internal class MTLParser
        {
            protected internal Dictionary<string, ObjMaterial> mMtrls;
            protected string mCurrentMtrlName;
            protected ObjMaterial mCurrentMtrl;

            protected internal MTLParser(string fileName)
            {
                mMtrls = new Dictionary<string, ObjMaterial>();
                mCurrentMtrl = default(ObjMaterial);

                ParseFile(ParseMtrlToken, fileName);

                if (mCurrentMtrlName != null)
                {
                    mMtrls.Add(mCurrentMtrlName, mCurrentMtrl);
                }
                mCurrentMtrlName = null;

            }
            protected static char[] omitchars = new char[] { ' ', '\r', '\n', '\t', '/' };
            protected void ParseFile(ParseHandler parser, string filename)
            {
                var reader = new StreamReader(filename);

                while (!reader.EndOfStream)
                {
                    string line = reader.ReadLine();

                    if (string.IsNullOrEmpty(line))
                        continue;

                    string[] tokens = line.Split(omitchars, StringSplitOptions.RemoveEmptyEntries);

                    if (tokens == null || tokens[0].StartsWith("#"))
                        continue;

                    parser(line, tokens);
                }
            }

            protected void ParseMtrlToken(string line, string[] tokens)
            {
                string token = tokens[0].ToLower();
                try
                {
                    if (string.IsNullOrWhiteSpace(line.Substring(token.Length)))
                        return;

                    switch (token)
                    {
                        case "newmtl":
                            {
                                if (mCurrentMtrlName != null)
                                {
                                    mMtrls.Add(mCurrentMtrlName, mCurrentMtrl);
                                }

                                mCurrentMtrlName = tokens[1];
                                mCurrentMtrl = new ObjMaterial();
                                break;
                            }
                        case "ns":
                            {
                                mCurrentMtrl.Ns = float.Parse(tokens[1], CultureInfo.InvariantCulture);
                                break;
                            }
                        case "ni":
                            {
                                mCurrentMtrl.Ni = float.Parse(tokens[1], CultureInfo.InvariantCulture);
                                break;
                            }
                        case "d":
                            {
                                mCurrentMtrl.d = float.Parse(tokens[1], CultureInfo.InvariantCulture);
                                break;
                            }
                        case "tr":
                            {
                                mCurrentMtrl.Tr = float.Parse(tokens[1], CultureInfo.InvariantCulture);
                                break;
                            }
                        case "illum":
                            {
                                mCurrentMtrl.illum = float.Parse(tokens[1], CultureInfo.InvariantCulture);
                                break;
                            }
                        case "tf":
                            {
                                float x = float.Parse(tokens[1], CultureInfo.InvariantCulture);
                                float y = float.Parse(tokens[2], CultureInfo.InvariantCulture);
                                float z = float.Parse(tokens[3], CultureInfo.InvariantCulture);
                                mCurrentMtrl.Tf = new Vector(x, y, z);
                                break;
                            }
                        case "ka":
                            {
                                float x = float.Parse(tokens[1], CultureInfo.InvariantCulture);
                                float y = float.Parse(tokens[2], CultureInfo.InvariantCulture);
                                float z = float.Parse(tokens[3], CultureInfo.InvariantCulture);
                                mCurrentMtrl.Ka = new Vector(x, y, z);
                                break;
                            }
                        case "kd":
                            {
                                float x = float.Parse(tokens[1], CultureInfo.InvariantCulture);
                                float y = float.Parse(tokens[2], CultureInfo.InvariantCulture);
                                float z = float.Parse(tokens[3], CultureInfo.InvariantCulture);
                                mCurrentMtrl.Kd = new Vector(x, y, z);
                                break;
                            }
                        case "ks":
                            {
                                float x = float.Parse(tokens[1], CultureInfo.InvariantCulture);
                                float y = float.Parse(tokens[2], CultureInfo.InvariantCulture);
                                float z = float.Parse(tokens[3], CultureInfo.InvariantCulture);
                                mCurrentMtrl.Ks = new Vector(x, y, z);
                                break;
                            }
                        case "ke":
                            {
                                float x = float.Parse(tokens[1], CultureInfo.InvariantCulture);
                                float y = float.Parse(tokens[2], CultureInfo.InvariantCulture);
                                float z = float.Parse(tokens[3], CultureInfo.InvariantCulture);
                                mCurrentMtrl.Ke = new Vector(x, y, z);
                                break;
                            }
                        case "map_d":
                        case "map_opacity":
                            {
                                var fileName = ProcessTexture(line.Substring(token.Length), tokens);
                                mCurrentMtrl.AlphaMap = fileName ?? tokens.Except(new[] { tokens.First() }).Aggregate((a, b) => a + ' ' + b);
                                break;
                            }
                        //case "map_ka":
                        //    {
                        //        var fileName = ProcessTexture(line.Substring(token.Length), tokens);
                        //        mCurrentMtrl.KdMap = fileName ?? tokens.Except(new[] { tokens.First() }).Aggregate((a, b) => a + ' ' + b);
                        //        break;
                        //    }
                        case "map_kd":
                            {
                                mCurrentMtrl.KdMap = Path.GetFileName(ProcessTexture(line.Substring(token.Length), tokens) ?? tokens.Except(new[] { tokens.First() }).Aggregate((a, b) => a + ' ' + b));
                                break;
                            }
                        case "map_refl":
                        case "map_ks":
                            {
                                mCurrentMtrl.KsMap = ProcessTexture(line.Substring(token.Length), tokens) ?? tokens.Except(new[] { tokens.First() }).Aggregate((a, b) => a + ' ' + b);
                                break;
                            }
                        case "decal":
                            {
                                mCurrentMtrl.DataMap = ProcessTexture(line.Substring(token.Length), tokens) ?? tokens.Except(new[] { tokens.First() }).Aggregate((a, b) => a + ' ' + b);
                                break;
                            }
                        case "bump":
                        case "map_bump":
                            {
                                //tokens.Last();
                                mCurrentMtrl.BumpMap = Path.GetFileName(ProcessTexture(line.Substring(token.Length), tokens) ?? tokens.Except(new[] { tokens.First() }).Aggregate((a, b) => a + ' ' + b));
                                break;
                            }
                        default:
                            break;
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Error parsing mtrl token: " + token);
                    Console.WriteLine(ex.Message + ex.StackTrace);
                }
            }

            private static bool IsFileNameValid(string fileName)
            {
                return
                    fileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1
                    ||
                fileName.IndexOfAny(Path.GetInvalidPathChars()) == -1;
            }

            private static string ProcessTexture(string fullLine, string[] tokens)
            {

                var merged = tokens.Skip(1).Aggregate((a, b) => a + b);
                if (IsFileNameValid(merged))
                {
                    return merged.Trim();
                }

                if (IsFileNameValid(tokens.Last()))
                {
                    return tokens.Last().Trim();
                }

                var fileNameExpr = new Regex(@"[^\\]*$",
    RegexOptions.IgnoreCase
    | RegexOptions.CultureInvariant
    | RegexOptions.IgnorePatternWhitespace
    | RegexOptions.Compiled);
                var lm = fileNameExpr.Match(fullLine);
                if (lm.Groups.Count > 0)
                {
                    return lm.Groups[0].Value.Trim();
                }
                string fileName = null;
                foreach (var parseToken in tokens.Skip(1))
                {
                    var matches = fileNameExpr.Match(parseToken);
                    if (matches.Length > 0)
                    {
                        fileName = matches.Groups[0].Value;
                        break;
                    }
                }
                return fileName.Trim();
            }
        }


        private static MaterialInfo CreateMaterial(string name, ObjMaterial material)
        {
            try
            {
                var res = new MaterialInfo()
                    {
                        Ka = material.Ka,
                        Kd = material.Kd,
                        Ks = material.Ks,
                        Ke = material.Ke,
                        Exponent = material.Ns,
                        Name = name
                    };
                if (!string.IsNullOrWhiteSpace(material.KaMap))
                {
                    res.Specular = new ImageTextureInfo()
                        {
                            FilePath = material.KaMap,
                            Name = Path.GetFileName(material.KaMap)
                        };
                }
                if (!string.IsNullOrWhiteSpace(material.KdMap))
                {
                    res.Diffuse = new ImageTextureInfo()
                        {
                            FilePath = material.KdMap,
                            Name = Path.GetFileName(material.KdMap)
                        };
                }

                if (!string.IsNullOrWhiteSpace(material.KsMap))
                {
                    res.Specular = new ImageTextureInfo()
                        {
                            FilePath = material.KsMap,
                            Name = Path.GetFileName(material.KsMap)
                        };
                }

                if (!string.IsNullOrWhiteSpace(material.BumpMap))
                {
                    res.BumpMap = new ImageTextureInfo()
                        {
                            FilePath = material.BumpMap,
                            Name = Path.GetFileName(material.BumpMap)
                        };
                }
                if (!string.IsNullOrWhiteSpace(material.AlphaMap))
                {
                    res.Alpha = new ImageTextureInfo()
                    {
                        FilePath = material.AlphaMap,
                        Name = Path.GetFileName(material.AlphaMap)
                    };
                }
                if (!string.IsNullOrWhiteSpace(material.NormalMap))
                {
                    res.NormalMap = new ImageTextureInfo()
                    {
                        FilePath = material.NormalMap,
                        Name = Path.GetFileName(material.NormalMap)
                    };
                }
                if (!string.IsNullOrWhiteSpace(material.DataMap))
                {
                    res.DataFile = material.DataMap;
                }
                return res;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

    }
}
